package mvc;

import java.util.LinkedList;
import java.util.List;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;

import depot.*;

public class ObserverManager implements Subject{
	
		private Customerlist allcustomer;
		private ParcelMap allparcels;
		private ArrayList<String> receiptList;
		private int count_Luxury, count_Normal;
		private String receipt;
		private int speed = 1000;		
		private int seqNo;
		private boolean state = true;
		
		/**
		 * the constructor of ObserverManager
		 */
		public ObserverManager(){
			
			allcustomer = new Customerlist();
			allparcels = new ParcelMap();
			receiptList = new ArrayList<String>();
			count_Luxury = 0;
			count_Normal = 0;
			seqNo = 1;
		}
		
		/**
		 * The synchronised get method to get state
		 * @return state
		 */
		public synchronized boolean getState()
		{
			return state;
		}
		
		/**
		 * The synchronised set method to set state
		 * @param state
		 */
		public synchronized void setState(boolean state)
		{
			this.state = state;
		}
		
		/**
		 * The synchronised addNewCustomer method add new customer objects into the system
		 * @param cm customer objects
		 */
		public synchronized void addNewCustomer(Customer cm)
		{
			allcustomer.addCustomer(cm);
			notifyObservers();
		}
		
		/**
		 * The synchronised get method to get sequence no for customer
		 * @return seqNo
		 */
		public synchronized int getSeqNo()
		{
			return seqNo;
		}
		
		/**
		 * The synchronised set method to set sequence no for customer
		 * @param seqNo
		 */
		public synchronized void setSeqNo(int seqNo)
		{
			this.seqNo = seqNo;
		}
		
		/**
		 * The get method to get the receipt after processing the data
		 * @return receipt
		 */
		public String getReceipt()
		{		
			return receipt;}
		
		/**
		 * The get method to get the arraylist containing all receipts
		 * @return receiptList
		 */
		public ArrayList<String> getReceiptList()
		{ return receiptList;}
		
		/**
		 * The get method to get the hashmap of parcels
		 * @return allparcels.getParcelMap()
		 */
		public HashMap<String, Parcel> getParcelList(){ return allparcels.getParcelMap();}
		
		/**
		 * The get method to get the arraylist of customers
		 * @return allcustomer.getCustomerList()
		 */
		public ArrayList<Customer> getCustomerList(){ return allcustomer.getCustomerList();}
		
		/**
		 * The method to remove customer object
		 * @param ID
		 */
		public void removeCustomer(String ID){
			allcustomer.removeCustomer(ID);
			notifyObservers();
		}
		
		/**
		 * The get method to get all customers
		 * @return allcustomer
		 */
		public Customerlist getAllcustomer(){ return allcustomer;}
		
		/**
		 * This method is used to calculate the amount of fee
		 * @param ID
		 * @param day
		 * @param weight
		 * @return parcelfee
		 */
		public double parcelFee(String ID, int day, double weight){
			
			double parcelfee = 0;
		 
			if(ID.substring(0, 1).equals("L")){
				if(weight<=5)
				{	parcelfee = weight*0.4 + day*0.5;
					  if(parcelfee>=50)
						  parcelfee =50;
					}
				else if(weight>5 && weight<10)
				{
					parcelfee = weight*0.6 + day*0.5;
					if(parcelfee>=50)
					  parcelfee = 50;
				}
				else if(weight>=10)
				{
					parcelfee = weight*0.8 + day*0.5;
					 if(parcelfee>=50)
						  parcelfee =50;				
			}
		}
			else if(ID.substring(0, 1).equals("N")){
				
				if(weight<=5)
				{	parcelfee = weight*0.2 + day*0.2;
					  if(parcelfee>=20)
						  parcelfee =20;
					}
				else if(weight>5 && weight<10)
				{
					parcelfee = weight*0.4 + day*0.2;
					if(parcelfee>=20)
					  parcelfee =20;
				}
				else if(weight>=10)
				{
					parcelfee = weight*0.6 + day*0.2;
					 if(parcelfee>=20)
						  parcelfee =20;				
			    }			
			}		
			
			parcelfee = (double)Math.round(parcelfee*100)/100;
				return parcelfee;
			
		}
		
		/**
		 * The get method to get speed
		 * @return speed
		 */
		public int getSpeed(){
			return speed;
		}
		
		/**
		 * The set method to set speed
		 * @param speed
		 */
		public void setSpeed(int speed){
			this.speed = speed;
			notifyObservers();
		}
		
		/**
		 * 
		 * @param ID
		 * @throws NoExistParcelException
		 */
		public synchronized void process(Customer cu, String workerName) throws NoExistParcelException{
			try{
				String ID = cu.getParcelID();
				Parcel p = allparcels.findParcel(ID);
				double price = 0;
				if(p != null){			
					price = this.parcelFee(ID, p.getDay(), p.getWeight());
				}	
				receipt = this.showReceipt(ID,price, cu, workerName);
				
				receiptList.add(receipt);		
				allparcels.removeParcel(ID);

			}
			catch (NoExistParcelException nepe)
			{
				System.out.println(nepe.getMessage());
			}
			
			notifyObservers();
			
		}
		
		/**
		 * This method is used to output the list of uncollected parcels
		 * @return report
		 */
		public String uncollectedParcel(){
			
			String report = "The list of uncollected parcels"+"\n";
			
			report += String.format("%-10s", "ParcelID");
			report += String.format("%-20s", "Days in Depot" );
			report += String.format("%-10s", "Weight");
			report += String.format("%-6s", "Dimensions");
			report += "\n";
			for (Map.Entry<String, Parcel> p : allparcels.getParcelMap().entrySet() ) {
				
				report += String.format("%-10s", p.getValue().getParcelID());
				report += String.format("%-20s", p.getValue().getDay());
				report += String.format("%-10s", p.getValue().getWeight());
				report += String.format("%-6s", p.getValue().getDimension());
				report += "\n";
			}
			report += "\n";
			return report;
		}
		
		/**
		 * This method is used to print out a single receipt
		 * @param ID
		 * @param price
		 * @return report
		 * @throws NoExistParcelException
		 */
		public String showReceipt(String ID,double price, Customer cu, String workerName) throws NoExistParcelException{
			String report ="";
			int seqno = cu.getSeqNo();
			String customername = cu.getName().getFullName();
			int day = allparcels.findParcel(ID).getDay();
			double weight = allparcels.findParcel(ID).getWeight();
			String dimension = allparcels.findParcel(ID).getDimension();
			
			report += "SeqNo: "+seqno +" | "+"Name: " + customername +" | "+ "ParcelID: "+ ID + "\n";
			report += "-------------------------------------------------\n";
			report += "Days: "+day +" | "+"Weight: " + weight +" | "+ "Dimensions: "+ dimension + "\n";
			report += "-------------------------------------------------\n";
			report += "Price: "+price +" |\n";
			report += "                                              Processed by : "+workerName +" \n";

			return report;
		}
		
		/**
		 * This method is used to print out the list of receipts
		 * @return report
		 */
		public String collectedParcel(){
			
			String report ="The table of collection receipts:" +"\n";
			for(String r : receiptList){
				report += r +"\n";
			}
				return report;
		}
		
		
		/**
		 * This method is used to show the numbers of collected and uncollected parcels
		 * @return report
		*/
		public String countStatusOfParcel(){
			String report = "The numbers of collected and uncollected parcels: \n";
		    report += String.format("%-20s","Status");
		    report += String.format("%-20s","Times");
		    report += "\n";
		    report += String.format("%-20s","Collected");
		    report += String.format("%-20s",receiptList.size());
		    report += "\n";
		    report += String.format("%-20s","Uncollected");
		    report += String.format("%-20s",allparcels.getSize());
		    return report;
		}
		
		/**
		 * This method is used to write the output of methods (uncollectedParcel(), collectedParcel()
		 * countStatusOfParcel() into a chosen file
		 * @param filename
		 * @throws NoExistParcelException
		 */
		public void writeToFile(String filename) throws NoExistParcelException {
			
			 FileWriter fw;
			 try {
			    fw = new FileWriter(filename);
			    fw.write("THE FINAL REPORT(XIMENG, JINGYING): \n\n");
			    fw.write(uncollectedParcel());
			    fw.write(collectedParcel());
			    fw.write(countStatusOfParcel());
			 	fw.close();
			 }
			 //message and stop if file not found
			 catch (FileNotFoundException fnf){
				 System.out.println(filename + " not found ");
				 System.exit(0);
			 }
			 //stack trace here because we don't expect to come here
			 catch (IOException ioe){
			    ioe.printStackTrace();
			    System.exit(1);
			 }
		}
		
		/**
		 * This method is used to read data from a chosen file
		 * @param filename
		 * @throws Exception
		 */
		public void readFile(String filename) throws Exception {
			try {
				//String type = filename.substring(0,filename.indexOf("."));
				File f = new File(filename);
				Scanner scanner = new Scanner(f);
				if(filename.equals("Customer")){
				while (scanner.hasNextLine()) {
					//read first line and process it
					String inputLine = scanner.nextLine(); 
					if (inputLine.length() != 0) //ignored if blank line
						allcustomer.processLine(inputLine);
					}
				}
				  else if(filename.equals("Parcel")){
					while (scanner.hasNextLine()) {
						//read first line and process it
						String inputLine = scanner.nextLine(); 
						if (inputLine.length() != 0) //ignored if blank line
							allparcels.processLine(inputLine);
						}
					} 
				notifyObservers();
			}
			//if the file is not found, stop with system exit
			catch (FileNotFoundException fnf){
				 System.out.println( filename + " not found ");
				 System.exit(0);
			 }
		}
		
		//OBSERVER PATTERN
		//SUBJECT must be able to register, remove and notify observers
		//list to hold any observers
		
		private List<Observer> registeredObservers = new LinkedList<Observer>();
		
		@Override
		public void registerObserver(Observer obs) {
			registeredObservers.add( obs);		
		}

		@Override
		public void removeObserver(Observer obs) {
			registeredObservers.remove( obs);
		}

		@Override
		public void notifyObservers() {
		for( Observer obs : registeredObservers)
			obs.update();
		}

}
